/**
 * Copyright 2005 SIB Framework

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */



package org.sibframework.utils;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;

/**
 * NIO file tool
 * @author song.chinamedcom.com
 *
 */
public class FileTool {

	public static final String DEFAULT_CHARSET = "UTF-8";
	/**NIO copy a file.
	 * 适合大量并发的二进制文件。
	 * @param source source file.
	 * @param target target file, must not exists.
	 * @throws Exception
	 */
	public void copyFile(File source, File target) throws IOException {
		if (!source.exists()) {
			throw new FileNotFoundException(source + " does not exists !");
		}
		if (!source.isFile()) {
			throw new IOException(source + "is not a file !");
		}

		if (target.exists()) {
			//throw new IOException(target + "is already exists !");
		}
		
		if (!target.getParentFile().exists()) {
			target.getParentFile().mkdirs();
		}
		
		
		target.createNewFile();
		FileInputStream fileInputStream = new FileInputStream(source);
		FileChannel sfc = fileInputStream.getChannel();
		FileOutputStream fileOutputStream = new FileOutputStream(target);
		FileChannel tfc = fileOutputStream.getChannel();
		sfc.transferTo(0, sfc.size(), tfc);
		sfc.close();
		//tfc.force(false);
		tfc.close();
		fileInputStream.close();
		fileOutputStream.close();
		return;
	}
	
	/**
	 * NIO save file 
	 * 适用于二进制文件效率很高。也可以处理文本文件但效率略低。
	 * @param is inputstream
	 * @param target
	 * @throws IOException
	 */
	public void saveFile(InputStream is, File target) throws IOException {
		if (is == null) {
			throw new NullPointerException("InputStream is null.");
		}
		if (!target.getParentFile().exists()) {
			target.getParentFile().mkdirs();
		}
		if (!target.exists()) {
			target.createNewFile();
		}
		ReadableByteChannel sfc = Channels.newChannel(is);
		FileOutputStream fileOutputStream = new FileOutputStream(target);
		FileChannel tfc = fileOutputStream.getChannel();
		tfc.transferFrom(sfc, 0, Integer.MAX_VALUE);
		sfc.close();
		tfc.force(false);
		tfc.close();
		fileOutputStream.close();
	}
	
	/**
	 * save file use nio channel
	 * 保存文件，基于nio的channel，适合高校处理二进制文件.也可以处理文本文件但效率略低。
	 * @param in
	 * @param target
	 * @throws IOException
	 */
	public void saveFile(byte[] in, File target) throws IOException {
		if (in == null) {
			throw new NullPointerException("InputStream is null.");
		}
		if (!target.getParentFile().exists()) {
			target.getParentFile().mkdirs();
		}
		if (!target.exists()) {
			target.createNewFile();
		}
		ReadableByteChannel sfc = Channels.newChannel(new ByteArrayInputStream(in));
		FileOutputStream fileOutputStream = new FileOutputStream(target);
		FileChannel tfc = fileOutputStream.getChannel();
		tfc.transferFrom(sfc, 0, Integer.MAX_VALUE);
		sfc.close();
		tfc.force(false);
		tfc.close();
		fileOutputStream.close();
	}
	
	/**
	 * Save string to file use reader and writer.<p>
	 * 采用NIO的管道技术保存文件。并发处理效率高。默认采用UTF-8字符集<p>
	 * 注意：不可以处理二进制文件，二进制文件不应该读取成String。
	 * @param str
	 * @param target
	 * @throws IOException
	 */
	public void saveTextFile(String str, File target) throws IOException {
		this.saveTextFile(str, target, DEFAULT_CHARSET);
	}
	

	/**
	 * Save string to file in a NIO channel way. 
	 * 采用NIO的管道技术保存文件。并发处理效率高<p>
	 * 注意：不可以处理二进制文件，二进制文件不应该读取成String。
	 * @param str 要保存的文本
	 * @param target 要保存的目标文件，会覆盖已有文件
	 * @param charset 字符集
	 * @throws IOException
	 */
	public void saveTextFile(String str, File target, String charset) throws IOException {
		if (str == null) {
			throw new NullPointerException("Inputm is null.");
		}
		if (!target.getParentFile().exists()) {
			target.getParentFile().mkdirs();
		}
		if (!target.exists()) {
			target.createNewFile();
		}
		
		if (charset == null) {
			charset = DEFAULT_CHARSET;
		}
		
		
		ReadableByteChannel sfc = Channels.newChannel(new ByteArrayInputStream(str.getBytes(charset)));
		FileOutputStream fileOutputStream = new FileOutputStream(target);
		FileChannel tfc = fileOutputStream.getChannel();

		tfc.transferFrom(sfc, 0, Integer.MAX_VALUE);
		sfc.close();
		tfc.force(false);
		tfc.close();
		fileOutputStream.close();
	}
	
	/***
	 * Save string to file use reader and writer.<p>
	 * 仅可以使用于文本文件保存。处理文本文件更好<p>
	 * 注意：不可以处理二进制文件，二进制文件不应该读取成String。
	 * @param str 文本内容
	 * @param target 目标文件
	 * @param charset 字符集
	 * @throws IOException
	 */
	public void saveTextFile2(String str, File target, String charset) throws IOException {
		if (str == null) {
			throw new NullPointerException("Inputm is null.");
		}
		if (!target.getParentFile().exists()) {
			target.getParentFile().mkdirs();
		}
		if (!target.exists()) {
			target.createNewFile();
		}
		
		if (charset == null) {
			charset = DEFAULT_CHARSET;
		}
		
		BufferedReader br = new BufferedReader(new StringReader(str));
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(target), charset));
		
		String line = br.readLine();
		while (line != null) {
			bw.write(line);
			bw.newLine();
			
			line = br.readLine();
		}
		
		bw.flush();
		bw.close();
		br.close();
	}
	

	/**
	 * NIO read file as a output stream
	 * 适用于读取二进制文件，基于NIO channel技术，并发性能更好。
	 * @param is
	 * @param target
	 * @throws IOException
	 */
	public void readFile(OutputStream os, File file) throws IOException {
		if (file == null || !file.exists()) {
			throw new NullPointerException("File not exists.");
		}

		FileInputStream fileInputStream = new FileInputStream(file);
		FileChannel sfc = fileInputStream.getChannel();
		WritableByteChannel tfc = Channels.newChannel(os);
		
		sfc.transferTo(0, Long.MAX_VALUE, tfc);
		os.flush();
		
		sfc.close();
		tfc.close();
		fileInputStream.close();
	}
	

	
	/**
	 * read file use nio channel<p>
	 * 读取文件，基于nio的channel<p>
	 * 适合高校处理二进制文件。
	 * @param file
	 * @return
	 * @throws IOException
	 */
	public byte[] readFile(File file) throws IOException {
		if (file == null || !file.exists()) {
			throw new NullPointerException("File not exists.");
		}

		FileInputStream fileInputStream = new FileInputStream(file);
		FileChannel sfc = fileInputStream.getChannel();
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		WritableByteChannel tfc = Channels.newChannel(baos);
		
		sfc.transferTo(0, Long.MAX_VALUE, tfc);
		baos.flush();
		byte[] result = baos.toByteArray();
		sfc.close();
		tfc.close();
		fileInputStream.close();
		
		
		return result;
	}
	
	
	/**
	 * read file as string, using NIO channel tech.<p>
	 * 仅可以用于文本文件的读取，基于NIO的channel技术，并发性能好。默认采用UTF-8字符集。
	 * 注意：不可以处理二进制文件，二进制文件不应该读取成String。
	 * @param file
	 * @return
	 * @throws IOException
	 */
	public String readTextFile(File file) throws IOException {
		return this.readTextFile(file, DEFAULT_CHARSET);
	}
	

	/**
	 * read file as string, using NIO channel tech.<p>
	 * 仅可以用于文本文件的读取，基于NIO的channel技术，并发性能好。
	 * 注意：不可以处理二进制文件，二进制文件不应该读取成String。
	 * @param file
	 * @param charset 字符集
	 * @return
	 * @throws IOException
	 */
	public String readTextFile(File file, String charset) throws IOException {
		if (file == null || !file.exists()) {
			throw new NullPointerException("File not exists.");
		}

		if (charset == null) {
			charset = DEFAULT_CHARSET;
		}
		
		FileInputStream fileInputStream = new FileInputStream(file);
		FileChannel sfc = fileInputStream.getChannel();
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		WritableByteChannel tfc = Channels.newChannel(baos);
		
		sfc.transferTo(0, Long.MAX_VALUE, tfc);
		baos.flush();
		String result = baos.toString(charset);
		sfc.close();
		tfc.close();
		fileInputStream.close();
		
		return result;
	}
	/**
	 * read file use char base reader an writer<p>
	 * 读取文件，使用基于char的reader和writer<p>
	 * 仅可以用于文本文件的读取。
	 * 注意：不可以处理二进制文件，二进制文件不应该读取成String。
	 * @param file 文件
	 * @param charset 字符集
	 * @return
	 * @throws IOException
	 */
	public String readTextFile2(File file, String charset) throws IOException {
		if (file == null || !file.exists()) {
			throw new NullPointerException("File not exists.");
		}

		if (charset == null) {
			charset = DEFAULT_CHARSET;
		}
		
		BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), charset));
		StringWriter sw = new StringWriter();
		BufferedWriter bw = new BufferedWriter(sw);

		String line = br.readLine();
		while (line != null) {

			bw.write(line);
			bw.newLine();
			
			line = br.readLine();
		}
		
		bw.flush();
		
		String result = sw.toString();
		
		br.close();
		bw.close();
		
		return result;
	}
	
	/**
	 * Copy a dir as a subdir in target dir.<p>
	 * @param source source dir.
	 * @param targetParent target dir may exists or not.
	 * @param deep 
	 * @param overide
	 * @throws Exception
	 */
	public void copyDirInto(File source, File targetParent, boolean deep, boolean overide) throws Exception {
		File targetf = new File(targetParent.getPath() + "\\" + source.getName());
		if (!targetf.exists()) {
			targetf.mkdirs();
		}
		this.copyDir(source, targetf, deep, overide);
	}	
	/**
	 * copy a dir to as target , 
	 * @param source source dir.
	 * @param target target dir, may exists or not.
	 * @param deep
	 * @param overide
	 * @throws Exception
	 */
	public void copyDir(File source, File target, boolean deep, boolean overide) throws Exception {
		if (!source.exists() ) {
			throw new FileNotFoundException(source + " does not exists!");
		}
		if (!source.isDirectory()) {
			throw new Exception(source + "is not a Directory !");
		}

		if (!target.exists() ) {
			target.mkdirs();
		}
		if (!target.isDirectory()) {
			throw new Exception(target + "is not a Directory !");
		}
			
		File[] files = source.listFiles();
		for (int i = 0; i < files.length; i++) {
			
			//get Target
			File targetf = new File(target.getPath() + "\\" + files[i].getName());
			
			//target exists
			if (targetf.exists()) {
				if (overide) {
					targetf.delete();
				} else {
					throw new FileNotFoundException(targetf.getPath() + " already exists!");
				}
			} 
			
			// Directory
			if (files[i].isDirectory()) {
				if (deep) {
					targetf.mkdir();
					copyDir(files[i], targetf, deep, overide);
					continue;
				} else {
					continue;
				}
			}
			
			//File Copy
			if (files[i].isFile()) {
				copyFile(files[i], targetf);
			}
		}

		if (!deep) {
			
		}
	}
	
	
	public static void main(String... strs) {
		File file = new File("c:/ASLog.txt");
		File file2 = new File("c:/ASLog2.txt");
		File file3 = new File("c:/ASLog3.txt");
		File file4 = new File("c:/ASLog4.txt");
		FileTool ft = new FileTool();

		long start = 0;
		int times = 500;
		
		
		Describer.describe(System.getProperties());
		
		try {
			
			
			start = System.currentTimeMillis();
			for (int i = 0 ; i < times; i++) {
				ft.copyFile(file, file2);
			}
			System.out.println("NIO Copy: " + (System.currentTimeMillis() - start)/1000d + "s.");
			


			start = System.currentTimeMillis();
			for (int i = 0 ; i < times; i++) {
				byte[] string = ft.readFile(file);
				ft.saveFile(string, file3);
			}
			System.out.println("Nio byte read/write: " + (System.currentTimeMillis() - start)/1000d + "s.");

			start = System.currentTimeMillis();
			for (int i = 0 ; i < times; i++) {
				String string = ft.readTextFile(file);
				ft.saveTextFile(string, file4);
			}
			System.out.println("reader String read/write: " + (System.currentTimeMillis() - start)/1000d + "s.");
			
			//System.out.println(string.length());
			//ft.saveFileNio(string, file2, "utf-8");
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println((System.currentTimeMillis() - start)/1000d + "s.");
		}
	}
}
